﻿#include "ModbusClient.h"
#include <QModbusTcpClient>
#include <QTimer>
#include <QDebug>
#include <QTime>
#include <QEventLoop>
#include <QDataStream>
#include <QModbusDataUnit>
#include <QElapsedTimer>
#include "ModbusEvent.h"

constexpr const int RECONNECT_COUNT_MAX = 3;
constexpr const int RESEND_COUNT_MAX = 2;
constexpr const int REQUEST_ERROR_COUNT_MAX = 2;

ModbusClient::ModbusClient(ModbusEvent* client, QObject* parent)
    : QObject(parent)
    , m_modbus(new QModbusTcpClient(this))
    , m_timer(new QTimer(this))
    , m_reConnectTimer(new QTimer(this))
    , m_client(client)
    , m_elapsedTimer(new QElapsedTimer)
{
    connect(m_modbus, &QModbusClient::stateChanged, this, &ModbusClient::stateChanged);
    connect(m_modbus, &QModbusDevice::errorOccurred, this, &ModbusClient::catchError);
    connect(m_timer, &QTimer::timeout, this, &ModbusClient::updateModbus);
    connect(m_reConnectTimer, &QTimer::timeout, this, &ModbusClient::reConnect);
}

ModbusClient::~ModbusClient() {
    m_timer->stop();
}

void ModbusClient::setConnect(const QString &host, quint16 port) {
    if (host != m_host) {
        m_host = host;
    }
    if (m_port != port) {
        m_port = port;
    }
}

bool ModbusClient::connectToServer() {
    if(m_host.isEmpty()){
        return false;
    }
    m_modbus->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_host);
    m_modbus->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
    if(!m_modbus->connectDevice()){        
        qDebug() << QStringLiteral("Connection is failed!");
        qDebug() << m_modbus->errorString();
        return false;
    }
    return true;
}

void ModbusClient::setTimeout(int timeout){
    m_modbus->setTimeout(timeout);
}

void ModbusClient::setDevice(int id){
    m_deviceID = id;
}

void ModbusClient::onMessage(QByteArray const& bytes) {
    if (m_modbus->state() != QModbusDevice::ConnectedState) {
        if (!m_isUnConnected) {
            qWarning() << QStringLiteral("modbus断开状态，消息不入队列");
            m_isUnConnected = true;
        }        
        return;
    }
    m_isUnConnected = false;
	QVector<quint16> values;
    int startAddress = -1; 
	QDataStream in(bytes);
    in >> values >> startAddress;
    if (!values.isEmpty() || startAddress == -1) {
        if (!m_sendMsg.isEmpty()) {
            return;
        }
        if (values.size() > 125) {
            //qDebug() << "values.size: " << values.size();
            for (int i = 0; i < values.size(); i+=125) {
				QModbusDataUnit datUnit(QModbusDataUnit::HoldingRegisters, startAddress + i, values.mid(i));
				m_sendMsg.enqueue(datUnit);
            }
        }
        else {
            //qDebug() << "input queue startaddr: " << startAddress << "   values:" << values;
			QModbusDataUnit datUnit(QModbusDataUnit::HoldingRegisters, startAddress, values);
			m_sendMsg.enqueue(datUnit);
        }
    }    
}

QVariant ModbusClient::connectionParameter(QModbusDevice::ConnectionParameter parameter) const {
    return m_modbus->connectionParameter(parameter);
}

void ModbusClient::catchError(QModbusDevice::Error error) {
    qWarning() << QStringLiteral("catch the error: ") + m_modbus->errorString();
    emit reportLogs(QStringLiteral("捕捉到错误: ") + m_modbus->errorString(), 2);
    if (error == QModbusDevice::ConnectionError || error == QModbusDevice::TimeoutError) {
        while (m_reConnectCount <= RECONNECT_COUNT_MAX) {
			m_reConnectCount++;
			if (m_reConnectCount > RECONNECT_COUNT_MAX) {
				qWarning() << QStringView(u"the try reconnect number of attempts reached the upper limit");
				qWarning() << QStringView(u"调整网络环境后,重启软件尝试.");
				m_sendMsg.clear();
				m_reConnectCount = 0;
                if (m_timer->isActive()) {
                    m_timer->stop();
                }
                m_modbus->disconnectDevice();              
                break;
			}			
        }
    }
}   

void ModbusClient::stateChanged(QModbusDevice::State state){    
    switch (state)
    {
    case QModbusDevice::UnconnectedState:
		qDebug() << QStringView(u"Client is unconnected!");
		if (!m_reConnectTimer->isActive())
			m_reConnectTimer->start(3000);
		if (m_timer->isActive())
			m_timer->stop();
		emit disconnected();
        break;
    case QModbusDevice::ConnectingState:
        qDebug() << QStringView(u"Client is connecting!");
        break;
    case QModbusDevice::ConnectedState:
		qDebug() << QStringView(u"Client is connected!");
		if (m_reConnectTimer->isActive())
			m_reConnectTimer->stop();
		if (!m_timer->isActive())
			m_timer->start(50);
        m_reConnectCount = 0;
		emit connected();
        break;
    case QModbusDevice::ClosingState:
		qDebug() << QStringView(u"Client is closing!");
		m_timer->stop();
		emit connectClosed();
        break;
    default:
        break;
    }
}

void ModbusClient::reConnect() {
    if (m_modbus->state() == QModbusDevice::ConnectedState) {
        return;
    }
	if (m_modbus->state() != QModbusDevice::ConnectingState) {
        emit reportLogs(QStringLiteral("尝试重新连接..."), 1);
        qDebug() << QStringView(u"尝试重新连接...");
		m_modbus->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_host);
		m_modbus->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
		m_modbus->connectDevice();	
	}
}

void ModbusClient::updateModbus(){
    int reSendCount = 0;
    bool repeatData = false;
    if (m_modbus->state() != QModbusDevice::ConnectedState) {
        if (!m_sendMsg.isEmpty()) {
            m_sendMsg.clear();
        }
        return;
    }
    QModbusDataUnit msg;
    if (!m_sendMsg.isEmpty()) {
        msg = m_sendMsg.head();
    }
    if (msg.startAddress() == m_lastWrite.startAddress() && msg.registerType() == m_lastWrite.registerType()) {
        bool allSame = false;
        auto lastValues = m_lastWrite.values();
        auto values = msg.values();
        if (lastValues == values && m_lastWrite.isValid() && msg.isValid()) {
            m_sendMsg.dequeue();
            repeatData = true;
        }
    }
    if (msg.isValid()) {
		if (!m_client->update(m_modbus, msg, m_deviceID)) {
			emit reportLogs(QStringLiteral("同步出错，查看连接状态正常！"), 1);
			qDebug() << QStringView(u"同步出错，查看连接状态正常！");
			setSuspectDisconnect(true);
			m_requestErrorCount++;
			if (m_requestErrorCount > REQUEST_ERROR_COUNT_MAX) {
				m_sendMsg.clear();
				m_modbus->disconnectDevice();
				emit disconnected();
				m_timer->stop();
				m_reConnectTimer->start(3000);
				setSuspectDisconnect(true);
			}
		}
		else {
            if (!repeatData) {
			    m_lastWrite = m_sendMsg.dequeue();
            }
			setSuspectDisconnect(false);
			m_requestErrorCount = 0;
		}
    }
    else {
		if (!m_client->read(m_modbus, m_deviceID)) {
			emit reportLogs(QStringLiteral("更新出错，查看连接状态正常！"), 1);
			qDebug() << QStringView(u"更新出错，查看连接状态正常！");
			setSuspectDisconnect(true);
			m_requestErrorCount++;
			if (m_requestErrorCount > REQUEST_ERROR_COUNT_MAX) {;
				m_modbus->disconnectDevice();
				emit disconnected();
				m_timer->stop();
				m_reConnectTimer->start(3000);
				setSuspectDisconnect(true);
			}
		}
		else {
			setSuspectDisconnect(false);
			m_requestErrorCount = 0;
		}
    }    
}

void ModbusClient::setSuspectDisconnect(bool isDisconnect) {
    if (m_isSuspectDisconnect != isDisconnect) {
        m_isSuspectDisconnect = isDisconnect;        
    }
	if (m_isSuspectDisconnect && m_requestErrorCount != m_lastRequestErrorCount) {
        m_lastRequestErrorCount = m_requestErrorCount;
		emit suspectNetwork(m_requestErrorCount);
	}
    if (!m_isSuspectDisconnect) {
        m_lastRequestErrorCount = 0;
    }
}
